<template>
  <div class="container">
    <div class="menu">
      <div class="menu-item">
        <span class="title">背景颜色: </span>
        <el-color-picker v-model="bgColor" size="small" show-alpha :predefine="predefineColors" />
        <!-- <input type="color" v-model="bgColor" /> -->
      </div>

      <div class="menu-item">
        <span class="title">绘画类型: </span>
        <el-select v-model="graphType" size="small">
          <el-option-group v-for="group in graphTypes" :key="group.label" :label="group.label">
            <el-option v-for="item in group.options" :key="item.value" :label="item.label" :value="item.value" />
          </el-option-group>
        </el-select>
      </div>
      <div class="menu-item">
        <span class="title">填充颜色: </span>
        <el-color-picker v-model="fillColor" size="small" show-alpha :predefine="predefineColors" />
        <!-- <input type="color" v-model="fillColor"/> -->
      </div>
      <div class="menu-item">
        <span class="title">线条颜色: </span>
        <el-color-picker v-model="strokeColor" size="small" show-alpha :predefine="predefineColors" />
        <!-- <input type="color" v-model="strokeColor"/> -->
      </div>
      <div class="menu-item">
        <span class="title"
          >线条大小: <span style="color: #409eff; font-weight: bold">{{ lineSize }}</span></span
        >
        <!-- <input type="range" v-model="lineSize" min="4" max="20" /> -->
        <el-button type="info" size="small" @click="lineSize < 40 && lineSize++">+</el-button>
        <el-button type="info" size="small" @click="lineSize > 0 && lineSize--">-</el-button>
      </div>
      <div class="menu-item">
        <span class="title">字体大小: {{ fontSize }}</span>
        <input type="range" v-model="fontSize" min="12" max="40" :disabled="graphType !== GRAPHT_YPES.TEXT" />
      </div>
      <div class="menu-item">
        <el-button type="primary" size="small" @click="zoom(1)">放大</el-button>
      </div>
      <div class="menu-item">
        <el-button type="primary" size="small" @click="zoom(-1)">缩小</el-button>
      </div>
      <div class="menu-item">
        <el-button type="primary" size="small" disabled>撤销</el-button>
      </div>
      <div class="menu-item">
        <el-button type="primary" size="small" disabled>恢复</el-button>
      </div>
      <div class="menu-item">
        <el-button type="primary" size="small" @click="savePng">保存为图片</el-button>
      </div>
      <div class="menu-item">
        <el-button type="primary" size="small" @click="removeGraph">删除所选图形</el-button>
      </div>
      <div class="menu-item">
        <el-button type="primary" size="small" @click="clearCanvas">清空画布</el-button>
      </div>
    </div>

    <div class="canvas-wrap" ref="canvasWrap">
      <canvas id="c"></canvas>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref, reactive, onMounted, onUnmounted, watchEffect } from "vue";
import { fabric } from "fabric";

import type { Canvas, IEvent, Textbox } from "fabric/fabric-impl";
import type { WatchStopHandle } from "vue";

// 启用橡皮擦功能需要额外引入 eraser_brush_mixin.js
import "./eraser_brush.mixin.js";

import { getDomWH } from "@/utils";
import { predefineColors, GRAPHT_YPES, drawTypes, graphTypes } from "./const";

let canvas: Canvas | null = null;
let context: CanvasRenderingContext2D | null = null;
let stop: WatchStopHandle | null = null;

// canvas 容器
const canvasWrap = ref<HTMLElement>();

// 背景色
const bgColor = ref("#00babd");
// 填充颜色
const fillColor = ref("rgba(0, 0, 0, 0)");
// 边框颜色
const strokeColor = ref("#ff0000");
// 线条大小
const lineSize = ref(4);
// 文字大小
const fontSize = ref(18);
// 画笔类型
const graphType = ref(GRAPHT_YPES.RECT);
// 当前是否处于绘图模式中(画布, 橡皮擦 除外)
const isDrawing = ref(false);
// 当前是否处于绘画状态下(鼠标是否已经点击)
const drawAction = ref(false);
// 临时存储绘画对象
const tempDraw = ref<any>(null);
// 临时存储文本对象
const tempText = ref<any>(null);

type Point = {
  x: number;
  y: number;
};

// 鼠标点击开始位置
const mouseFrom = reactive<Point>({ x: 0, y: 0 });
// 鼠标移动的位置
const mouseTo = reactive<Point>({ x: 0, y: 0 });

onMounted(() => {
  init();
});

const handleResize = () => {
  if (canvas && canvasWrap.value) {
    const { width, height } = getDomWH(canvasWrap.value);
    canvas.setWidth(width);
    canvas.setHeight(height);
    canvas.setBackgroundColor(bgColor.value, () => {});
    canvas.renderAll();
  }
};

const init = () => {
  if (!canvasWrap.value) return;

  // fabric.Canvas 可以通过鼠标选中图像进行缩放, 旋转的
  // fabric.StaticCanvas 没有鼠标交互的
  canvas = new fabric.Canvas("c", {
    // 因为 canvas 宽高会丢失, 这里动态的设置
    ...getDomWH(canvasWrap.value)
  });

  // 或者单独设置宽高
  // canvas.setWidth(100); // setHeight

  // 获取上下对象
  context = canvas.getContext();

  console.log("canvas: ", canvas);

  window.addEventListener("resize", handleResize);

  stop = watchEffect(
    () => {
      if (!canvas) return;

      // canvas.freeDrawingBrush.width

      canvas.setBackgroundColor(bgColor.value, () => {});
      // 画布
      if (graphType.value === GRAPHT_YPES.BRUSH) {
        onDrawing();

        // 橡皮擦
      } else if (graphType.value === GRAPHT_YPES.ERASER) {
        onEraserBrush();

        // 文本
      } else if (graphType.value === GRAPHT_YPES.TEXT) {
        if (canvas && tempText.value) {
          canvas.selection = true;
          canvas.isDrawingMode = false;
        }

        // 移动画布
      } else if (graphType.value === GRAPHT_YPES.MOVE) {
        if (canvas) {
          canvas.isDrawingMode = false;
        }

        // 绘画
      } else if (drawTypes.includes(graphType.value)) {
        isDrawing.value = true;
        if (canvas) {
          canvas.selection = false; // 禁止所有选中
          canvas.isDrawingMode = false; // 关闭自由绘画模式
        }
      }

      canvas.renderAll();
    },
    { flush: "post" }
  );

  bindEvent();
};

onUnmounted(() => {
  if (stop) stop();
  window.removeEventListener("resize", handleResize);
});

// 开启画布自由绘画模式
const onDrawing = () => {
  if (!canvas) return;

  // 开启画布自由绘画模式
  canvas.isDrawingMode = true;
  // 设置自由绘画模式画笔类型为 铅笔类型
  canvas.freeDrawingBrush = new fabric.PencilBrush(canvas);
  // 画笔颜色
  canvas.freeDrawingBrush.color = strokeColor.value;
  // 画笔线条大小
  canvas.freeDrawingBrush.width = lineSize.value;
};

// 开启橡皮擦模式
const onEraserBrush = () => {
  if (!canvas) return;

  // 启用自由绘画模式
  canvas.isDrawingMode = true;
  // 自由绘画模式 画笔类型设置为 橡皮擦对象
  canvas.freeDrawingBrush = new fabric.EraserBrush(canvas);
  // 设置橡皮擦大小
  canvas.freeDrawingBrush.width = lineSize.value * 2;
};

// 删除选择的图形
const removeGraph = () => {
  if (canvas) {
    const activeObjects = canvas.getActiveObjects();
    for (const item of activeObjects) {
      canvas.remove(item);
    }
  }
};

// 清空画布
const clearCanvas = () => {
  if (canvas) canvas.clear();
};

const bindEvent = () => {
  if (!canvas) return;

  canvas.on("mouse:down", (options: IEvent<MouseEvent>) => {
    // console.log("mouse:down: ", options);

    if (canvas && options.pointer) {
      // 记录当前鼠标的起点坐标
      mouseFrom.x = options.pointer.x;
      mouseFrom.y = options.pointer.y;
      // mouseFrom.x = options.e.clientX - canvas._offset.left;
      // mouseFrom.y = options.e.clientY - canvas._offset.top;

      if (graphType.value === GRAPHT_YPES.TEXT) {
        if (options.target && options.target.type === "textbox") {
          (options.target as Textbox).enterEditing();
          tempText.value = options.target;
          return;
        }
        drawText();
      } else if (isDrawing.value) {
        // 当前不是选中图像时才可以进行绘制
        if (!options.target) {
          drawAction.value = true;
        }
      }
    }
  });

  canvas.on("mouse:move", (options: IEvent<MouseEvent>) => {
    // console.log("mouse:move: ", options);
    if (canvas && options.pointer && drawAction.value) {
      // 记录当前鼠标移动终点坐标
      mouseTo.x = options.pointer.x;
      mouseTo.y = options.pointer.y;
      // mouseTo.x = options.e.clientX - canvas._offset.left;
      // mouseTo.y = options.e.clientY - canvas._offset.top;

      if (graphType.value === GRAPHT_YPES.MOVE) {
        moveCanvas(); // 移动画布
        return;
      }

      // 如果当前图形已绘制, 清除上一次绘制的图形
      if (tempDraw.value) {
        canvas.remove(tempDraw.value);
      }

      switch (graphType.value) {
        case GRAPHT_YPES.LINE: // 线
          drawLine();
          break;
        case GRAPHT_YPES.RECT: // 矩形
          drawRect();
          break;
        case GRAPHT_YPES.CIRCLE: // 圆
          drawCircle();
          break;
      }

      // 将绘制的图形添加到画布中
      if (drawTypes.includes(graphType.value)) {
        canvas.add(tempDraw.value);
      }
    }
  });

  canvas.on("mouse:up", (options: IEvent<MouseEvent>) => {
    // console.log("mouse:down: ", options);
    if (isDrawing.value) {
      drawAction.value = false;

      if (tempDraw.value && canvas) {
        tempDraw.value = null;
      }
    }
  });
};

// 绘制方法
const draw = () => {
  if (!canvas) return;

  // 如果当前图形已绘制, 清除上一次绘制的图形
  if (tempDraw.value) {
    canvas.remove(tempDraw.value);
  }

  // 添加到画布中
  canvas.add(tempDraw.value);
};

// 绘制直线
const drawLine = () => {
  tempDraw.value = new fabric.Line([mouseFrom.x, mouseFrom.y, mouseTo.x, mouseTo.y], {
    fill: fillColor.value,
    stroke: strokeColor.value,
    strokeWidth: lineSize.value
  });
};

// 绘制矩形
const drawRect = () => {
  const width = getWidth();
  const height = getHeight();
  const left = getTransformedPosX(mouseFrom.x);
  const top = getTransformedPosY(mouseFrom.y);

  tempDraw.value = new fabric.Rect({
    left, // 距离上边的距离
    top, // 距离左边的距离
    width, // 矩形宽度
    height, // 矩形高度
    fill: fillColor.value, // 填充的颜色
    stroke: strokeColor.value, // 描边颜色
    strokeWidth: lineSize.value // 线条大小
  });
};

// 绘制圆形
const drawCircle = () => {
  const left = getTransformedPosX(mouseFrom.x);
  const top = getTransformedPosY(mouseFrom.y);

  // 计算圆的半径
  const radius =
    Math.sqrt(
      (getTransformedPosX(mouseTo.x) - left) * (getTransformedPosY(mouseTo.x) - left) +
        (getTransformedPosX(mouseTo.y) - top) * (getTransformedPosY(mouseTo.y) - top)
    ) / 2;

  tempDraw.value = new fabric.Circle({
    left: left,
    top: top,
    stroke: strokeColor.value,
    fill: fillColor.value,
    radius: radius,
    strokeWidth: lineSize.value
  });
};

// 缩放
const zoom = (flag: number) => {
  if (canvas) {
    let zoom = canvas.getZoom();
    if (flag > 0) {
      // 放大
      zoom *= 1.1;
    } else {
      // 缩小
      zoom *= 0.9;
    }
    // zoom 不能大于 20 不能小于0.01
    zoom = zoom > 20 ? 20 : zoom;
    zoom = zoom < 0.01 ? 0.01 : zoom;
    canvas.setZoom(zoom);
  }
};

// 绘制文本
const drawText = () => {
  if (!canvas) return;

  if (!tempText.value) {
    // 当前不存在绘制中的图形对象, 鼠标第一次按下

    // 根据鼠标按下的起点坐标文本对象
    tempText.value = new fabric.Textbox("", {
      left: getTransformedPosX(mouseFrom.x),
      top: getTransformedPosY(mouseFrom.y),
      fontSize: fontSize.value,
      fill: strokeColor.value,
      hasControls: false,
      editable: true,
      width: 30,
      backgroundColor: "#fff",
      selectable: false
    });
    canvas.add(tempText.value);
    // 文本打开编辑模式
    tempText.value.enterEditing();
    // 文本编辑框获取焦点
    tempText.value.hiddenTextarea.focus();
  } else {
    // 鼠标第二次按下 将当前文本对象退出编辑模式
    tempText.value.exitEditing();
    tempText.value.set("backgroundColor", "rgba(0,0,0,0)");
    if (tempText.value.text == "") {
      canvas.remove(tempText.value);
    }
    canvas.renderAll();
    tempText.value = null;
  }
};

// 移动画布
const moveCanvas = () => {
  if (canvas && canvas.viewportTransform) {
    // 获取当前画布视口移动对象
    // 通过计算鼠标的距离修改视口左上角的坐标
    const vpt = canvas.viewportTransform;
    vpt[4] += mouseTo.x - mouseFrom.x;
    vpt[5] += mouseTo.y - mouseFrom.y;
    // 视口坐标修改完成后对画布进行重新渲染
    canvas.requestRenderAll();
    mouseFrom.x = mouseTo.x;
    mouseFrom.y = mouseTo.y;
  }
};

const getWidth = () => {
  if (mouseTo.x > mouseFrom.x) {
    return mouseTo.x - mouseFrom.x; // 左上向右下
  } else {
    return mouseFrom.x - mouseTo.x; // 右下向左上
  }
};

const getHeight = () => {
  if (mouseTo.y > mouseFrom.y) {
    return mouseTo.y - mouseFrom.y; // 左上向右下
  } else {
    return mouseFrom.y - mouseTo.y; // 右下向左上
  }
};

// 计算画布移动之后的x坐标点
const getTransformedPosX = (x: number) => {
  if (canvas && canvas.viewportTransform) {
    const zoom = Number(canvas.getZoom());
    return (x - canvas.viewportTransform[4]) / zoom;
  } else {
    return 0;
  }
};

// 计算画布移动之后的y坐标点
const getTransformedPosY = (y: number) => {
  if (canvas && canvas.viewportTransform) {
    const zoom = Number(canvas.getZoom());
    return (y - canvas.viewportTransform[5]) / zoom;
  } else {
    return 0;
  }
};

// 保存为 png
const savePng = () => {
  if (!canvas) return;

  // 通过克隆画布去实现，不修改原画布
  canvas.clone((cvs: Canvas) => {
    //遍历所有对对象，获取最小坐标，最大坐标，根据画布中图形的最小坐标与最大坐标计算导出内容的左上角坐标以及导出内容的宽高
    let top = 0;
    let left = 0;
    let width = canvas?.width;
    let height = canvas?.height;

    const objects = cvs.getObjects();
    if (objects.length > 0) {
      let rect = objects[0].getBoundingRect();
      let minX = rect.left;
      let minY = rect.top;
      let maxX = rect.left + rect.width;
      let maxY = rect.top + rect.height;
      for (let i = 1; i < objects.length; i++) {
        rect = objects[i].getBoundingRect();
        minX = Math.min(minX, rect.left);
        minY = Math.min(minY, rect.top);
        maxX = Math.max(maxX, rect.left + rect.width);
        maxY = Math.max(maxY, rect.top + rect.height);
      }
      // 给上下左右添加100px的间距，这样导出的图形不会刚好贴合最边缘的图形而是有一定的间隙
      top = minY - 100;
      left = minX - 100;
      width = maxX - minX + 200;
      height = maxY - minY + 200;
      // 给当前画布添加颜色与背景色相同的矩形，解决初始视口外区域底色为透明的问题。
      cvs.sendToBack(
        new fabric.Rect({
          left,
          top,
          width,
          height,
          stroke: "rgba(0,0,0,0)",
          fill: bgColor.value,
          strokeWidth: 0
        })
      );
    }
    // 将画布导出成base64格式数据
    const dataURL = cvs.toDataURL({
      format: "png",
      multiplier: cvs.getZoom(),
      left,
      top,
      width,
      height
    });

    const link = document.createElement("a");
    link.setAttribute("download", "canvas.png");
    link.setAttribute("href", dataURL);
    link.style.display = "none";
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  });
};

// 设置图形的选择功能
const setDrawSelectable = (flog: boolean) => {
  if (canvas) {
    const drawObjects = canvas.getObjects();
    if (drawObjects.length > 0) {
      drawObjects.map(item => {
        item.set("selectable", flog);
      });
    }
  }
};

// 创建常用的图形
const createHraph = () => {
  if (!canvas) return;

  // 创建一个正方形
  const rect = new fabric.Rect({
    left: 0, // 距离上边的距离
    top: 200, // 距离左边的距离
    width: 100, // 矩形宽度
    height: 100, // 矩形高度
    fill: "green" // 填充的颜色
  });

  // 添加到画布中
  canvas.add(rect);

  // 创建一个圆形对象
  const circle = new fabric.Circle({
    left: 0, // 距离左边的距离
    top: 0, // 距离上边的距离
    fill: "red", // 填充的颜色
    radius: 50 // 圆的半径
  });

  // 创建一个三角形对象
  const triangle = new fabric.Triangle({
    left: 200, // 距离左边的距离
    top: 0, // 距离上边的距离
    fill: "blue", // 填充的颜色
    width: 100, // 宽度
    height: 100 // 高度
  });

  // 添加到画布中(可以添加多个)
  canvas.add(circle, triangle);

  // canvas.selection = false; // 禁止所有选中
  // rect.set("selectable", false); // 单独禁止这个矩形选中
};
</script>

<style lang="scss" scoped>
@import "@/styles/var.scss";

.container {
  width: 100%;
  height: 82vh;

  .menu {
    height: 60px;
    padding: 4px;
    box-sizing: border-box;
    // border: 1px solid green;
    display: flex;
    flex-wrap: wrap;

    .menu-item {
      margin-right: 20px;
      position: relative;
      display: flex;
      align-items: center;

      .title {
        font-size: 12px;
        color: rgb(56, 55, 55);
        margin-right: 6px;
      }

      :deep(.el-input) {
        width: 74px;
      }
    }
  }

  .canvas-wrap {
    height: calc(100% - 40px);
    // border: 1px solid red;
    box-shadow: $shadow;
  }
}
</style>
